﻿using System.Reflection.Emit;
using System.Reflection;
using linq = Microsoft.Linq.Expressions;
using System.Collections;
using System.Collections.Generic;
using System;
using Bling.Util;
namespace Bling.NewGen {
  public abstract class Generator {
    internal readonly ModuleBuilder Module;
    public Generator(string Name) {
      AppDomain Domain = System.Threading.Thread.GetDomain();
      AssemblyBuilder AsmBuilder = Domain.DefineDynamicAssembly(new AssemblyName() { Name = Name }, AssemblyBuilderAccess.RunAndSave);
      Module = AsmBuilder.DefineDynamicModule(Name + "Module", true);
    }
  }
  public abstract class Generator<Q,R> : Generator {
    public Generator(string Name) : base(Name) { }

    public abstract class UseBaseClass<S, T> where S : Q where T : R {
      public readonly S Underlying;
      public UseBaseClass(S Underlying) {
        this.Underlying = Underlying;
      }
    }


    public abstract Func<S, K> GetProperty<S, T, K>(PropertyInfo Property)
      where S : Q
      where T : R;
    public abstract Action<S, K> SetProperty<S,T,K>(PropertyInfo Property) where S : Q where T : R;
    // could be a function or action. 



    protected abstract object CallMethod<S, T>(MethodInfo Method)
      where S : Q
      where T : R;


    protected abstract IList<Type> Interfaces<S, T>()
      where S : Q
      where T : R;
    protected abstract Type Base<S,T>() where S : Q where T : R; // must have constructor that takes S

    private static class Holder<S, T>
      where S : Q
      where T : R {
      public static Func<S, T> Create;
    }
    public T Create<S, T>(S s) where S : Q where T : R {
      if (Holder<S, T>.Create == null) {
        Holder<S, T>.Create = Create0<S, T>();
      }
      return Holder<S, T>.Create(s);
    }
    private int ClassCount = 0;
    protected virtual Func<S, T> Create0<S, T>()
      where S : Q
      where T : R {
      var BaseType = Base<S, T>();
      var Bld = Module.DefineType(typeof(T).Name + "Impl" + ClassCount, TypeAttributes.Public, BaseType);
      ClassCount += 1;
      var BaseField = BaseType.GetField("Underlying");
      (BaseField != null).Assert();
      (BaseField.FieldType == typeof(S)).Assert();
      { // do constructor first
        var BaseCall = BaseType.GetConstructor(new Type[] { typeof(S) });
        (BaseCall != null).Assert();
        var Constr = Bld.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(S) });
        var il = Constr.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0); // load self.
        // pass through parameters.
        il.Emit(OpCodes.Ldarg_1); // load S
        il.Emit(OpCodes.Call, BaseCall);
        il.Emit(OpCodes.Ret);
      }
      var DoSet = new Dictionary<string, object>();
      foreach (var Intf in Interfaces<S, T>()) {
        Bld.AddInterfaceImplementation(Intf);
        foreach (var Meth in Intf.GetMethods()) {
          if (Meth.IsSpecialName) continue;
          var ParamTypes = new Type[Meth.GetParameters().Length];
          for (int i = 0; i < ParamTypes.Length; i++) {
            ParamTypes[i] = Meth.GetParameters()[i].ParameterType;
          }
          var NewMeth = Bld.DefineMethod(Meth.Name, MethodAttributes.Public | MethodAttributes.Virtual,
            Meth.ReturnType, ParamTypes);
          Bld.DefineMethodOverride(NewMeth, Meth);
          var Call = CallMethod<S, T>(Meth);

          var ParamTypes0 = new Type[ParamTypes.Length + 1];
          ParamTypes0[0] = typeof(S);
          for (int i = 0; i < ParamTypes.Length; i++) ParamTypes0[i + 1] = ParamTypes[i];
          var Fld = Bld.DefineField(Meth.Name + "Call0", Call.GetType(), FieldAttributes.Static | FieldAttributes.Public);
          DoSet[Fld.Name] = Call;
          var Invoke = Call.GetType().GetMethod("Invoke", ParamTypes0);
          (Invoke != null).Assert();
          (Invoke.ReturnType == Meth.ReturnType).Assert();
          {
            var il = NewMeth.GetILGenerator();
            il.Emit(OpCodes.Ldsfld, Fld); // load the static field.
            il.Emit(OpCodes.Ldarg_0); // load this, which is a sub-type of T.
            il.Emit(OpCodes.Ldfld, BaseField); // now a sub-type of S.

            for (int i = 0; i < ParamTypes.Length; i++) {
              il.Emit(OpCodes.Ldarg, i + 1);
            }
            il.Emit(OpCodes.Callvirt, Invoke);
            il.Emit(OpCodes.Ret);
          }
        }
        var GetPropertyG = GetType().GetMethod("GetProperty" /*, new Type[] { typeof(PropertyInfo) }*/);
        var SetPropertyG = GetType().GetMethod("SetProperty", new Type[] { typeof(PropertyInfo) });

        foreach (var Prop in Intf.GetProperties()) {
          var NewProp = Bld.DefineProperty(Prop.Name, Prop.Attributes, Prop.PropertyType, new Type[0]);
          if (Prop.CanRead) {
            var GetProperty = GetPropertyG.MakeGenericMethod(typeof(S), typeof(T), Prop.PropertyType);
            var Load = GetProperty.Invoke(this, new object[] { Prop });

            var Fld = Bld.DefineField(Prop.Name + "Get0", Load.GetType(), FieldAttributes.Static | FieldAttributes.Public);
            DoSet[Fld.Name] = Load;
            var Invoke = Load.GetType().GetMethod("Invoke", new Type[] { typeof(S) });
            (Invoke.ReturnType == Prop.PropertyType).Assert();
            var Getter = Bld.DefineMethod("get_" + Prop.Name,
              MethodAttributes.Public | MethodAttributes.HideBySig |
              MethodAttributes.SpecialName | MethodAttributes.Virtual, Prop.PropertyType, null);
            {
              var il = Getter.GetILGenerator();
              il.Emit(OpCodes.Ldsfld, Fld); // load the static field.
              il.Emit(OpCodes.Ldarg_0); // load this, which is a sub-type of T.
              il.Emit(OpCodes.Ldfld, BaseField); // get instance of S
              il.Emit(OpCodes.Callvirt, Invoke);
              il.Emit(OpCodes.Ret);
            }
            NewProp.SetGetMethod(Getter);
          }
          if (Prop.CanWrite) {
            var SetProperty = SetPropertyG.MakeGenericMethod(typeof(S), typeof(T), Prop.PropertyType);
            var Store = SetProperty.Invoke(this, new object[] { Prop });
            var Fld = Bld.DefineField(Prop.Name + "Set0", Store.GetType(), FieldAttributes.Static | FieldAttributes.Public);
            DoSet[Fld.Name] = Store;
            var Invoke = Store.GetType().GetMethod("Invoke", new Type[] { typeof(S), Prop.PropertyType });
            (Invoke.ReturnType == typeof(void)).Assert();
            var Setter = Bld.DefineMethod("set_" + Prop.Name, MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual,
              null, new Type[1] { Prop.PropertyType });
            {
              var il = Setter.GetILGenerator();
              il.Emit(OpCodes.Ldsfld, Fld);
              il.Emit(OpCodes.Ldarg_0); // load this
              il.Emit(OpCodes.Ldfld, BaseField); // get instance of S

              il.Emit(OpCodes.Ldarg_1); // load the argument
              // now call invoke.
              il.Emit(OpCodes.Callvirt, Invoke);
              il.Emit(OpCodes.Ret);
            }
            NewProp.SetGetMethod(Setter);
          }
        }
      }
      var TypeS = Bld.CreateType();
      // set all static fields
      foreach (var k in DoSet) {
        var fld = TypeS.GetField(k.Key);
        (fld != null).Assert();
        fld.SetValue(null, k.Value);
      }
      var Constructor = TypeS.GetConstructor(new Type[] { typeof(S) });
      return (s) => (T)Constructor.Invoke(new object[] { s });
    }
  }
  

}